/**
 * Copyright (C) 2009-2012, KingHool.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses.
 */
package com.kinghool.smodel.def;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.kinghool.ebos.commons.utils.Assert;
import com.kinghool.ebos.commons.utils.Utils;
import com.kinghool.smodel.IModel;
import com.kinghool.smodel.ValidationResult;
import com.kinghool.smodel.rule.IModelRule;
import com.kinghool.smodel.rule.PropertyMandantoryRule;

/**
 * 
 * @Author Allen Deng(dengtailin@gmail.com) 
 */
public class ModelDefinition implements Cloneable {
	protected String id;
	protected boolean caseSensitive = true;
	protected boolean ignoreCheck = false;
	
	protected List<String> props = Utils.newList();   
	protected Map<String, String> propDescriptions = Utils.newMap();
	protected Map<String, Object> propValues = Utils.newMap();
	protected Map<String, IValueProvider> propValueProviders = Utils.newMap();
 
	protected List<IModelRule> rules = Utils.newList();
	
	protected String description;
 
	public ModelDefinition(String id) {
		this.id = id;
	} 
	
	public static ModelDefinition createDefaultModelDefinition(String id) {
		ModelDefinition modelDef = new ModelDefinition(id);
		modelDef.setCaseSensitive(true);
		modelDef.setIgnoreCheck(true);
		return modelDef;
	}
	
	public Object getDefaultValue(String property) {
		property = formatPropertyName(property);
		return propValues.get(property);
	}
	
	public IValueProvider getPropertyValueProvider(String property) {
		property = formatPropertyName(property);
		return propValueProviders.get(property);
	}
	
	public void setPropertyDescription(String property, String description) {
		this.propDescriptions.put(property, description);
	}
	
	public String getPropertyDescription(String property) {
		return propDescriptions.get(property);
	}
	
	public ValidationResult validate(IModel model) {
		return ValidationResult.valueOf(getErrorMessages(model), 
				                        getWarningMessages(model));
	}

	public String getId() {
		return id;
	}

	public boolean isCaseSensitive() {
		return caseSensitive;
	}
	
	public void setCaseSensitive(boolean caseSensitive) {
		this.caseSensitive = caseSensitive;
	}
	
	/**
	 * check the given property is the one of the model class
	 * if the ignoreCheck is set true, then any property could be installed in the model
	 */
	public boolean isProperty(String propertyName) {
		return isIgnoreCheck() ? true : props.contains(formatPropertyName(propertyName));
	} 
	
	/**
	 * get the definition sequence of the properties for the model
	 */
	public List<String> getPropertySequence() {
		return new ArrayList<String>(props);
	}
	
	protected String formatPropertyName(String propertyName) {
		return caseSensitive ? propertyName : propertyName.toLowerCase();
	}

	public boolean isIgnoreCheck() {
		return ignoreCheck;
	}

	public void setIgnoreCheck(boolean ignoreCheck) {
		this.ignoreCheck = ignoreCheck;
	}	
	
	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public void installRule(IModelRule rule) {
		this.rules.add(rule);
	}
	
	public boolean isPropertyMandantory(String property) {  
		property = formatPropertyName(property);
		for (IModelRule rule : rules) {
			if (rule instanceof PropertyMandantoryRule) {
				if (((PropertyMandantoryRule)rule).isMandatory(property)) {
					return true;
				}
			}
		}
		return false;
	} 
	
	public void installProperty(String property, Object initValue, IValueProvider valueProvider) {
		property = formatPropertyName(property);
		if (! Assert.isNullOrEmpty(property) && ! props.contains(property)) {
			props.add(property);

			if (initValue != null) {
				propValues.put(property, initValue);
			}
			 
			if (valueProvider != null) {  
				propValueProviders.put(property, valueProvider);
			}
		}
	}
	
	public void installProperty(String property) {
		installProperty(property, null, null);
	}
	
	public void installProperty(String property, Object initValue) {
		installProperty(property, initValue, null);
	}
	
	public void installMandantoryProperty(String property) {
		installProperty(property, null, null, true);
	}
	
	public void installMandantoryProperty(String property, Object initValue) {
		installProperty(property, initValue, null, true);
	}
	
	public void installMandantoryProperty(String property, Object initValue, IValueProvider valueProvider) {
		installProperty(property, initValue, valueProvider, true);
	}
	
	public void installProperty(String property, Object initValue, IValueProvider valueProvider, boolean mandantory) {
		installProperty(property, initValue, valueProvider, IModelRule.SEVERITY_ERROR, mandantory);
	}
	
	public void installProperty(String property, Object initValue, IValueProvider valueProvider, int severity, boolean mandantory) {
        property = formatPropertyName(property);
		installProperty(property, initValue, valueProvider);
		if (mandantory) {
			this.installRule(new PropertyMandantoryRule(severity, new String[]{property}));
		}
	}
	
	protected List<String> getWarningMessages(IModel model) {
		List<String> warnMsgs = Utils.newList();
		
		for (IModelRule rule : rules) {
			if (rule.getSeverity() == IModelRule.SEVERITY_WARNING) {
				List<String> ruleWarMsgs = rule.validate(model);
				if (!Assert.isNullOrEmpty(ruleWarMsgs)) {
					warnMsgs.addAll(ruleWarMsgs);
				}
			}
		}
		return warnMsgs;
	}
	
	protected List<String> getErrorMessages(IModel model) {
		List<String> errMsgs = Utils.newList(); 
		
		//check the attributes whether defined, error if not defined.
		boolean valid = ignoreCheck ? true : props.containsAll(model.getAllProperties());		
		if (! valid) {
			List<String> dest = new ArrayList<String>(model.getAllProperties()); 
			dest.removeAll(props);
			errMsgs.add("Bad format for the model:\n" +
					       "The model ["+ model.toString() + "] has undefined attributes:" + dest);
		} 
		

		//check the error level rules defined, error if failed
		for (IModelRule rule : rules) {
			if (rule.getSeverity() == IModelRule.SEVERITY_ERROR) {
				List<String> ruleErrMsgs = rule.validate(model);
				if (!Assert.isNullOrEmpty(ruleErrMsgs)) {
					errMsgs.addAll(ruleErrMsgs);
				}
			}
		}
		return errMsgs;
	}
	
	@SuppressWarnings("unchecked")
	public ModelDefinition clone() throws CloneNotSupportedException {
		ModelDefinition clone = (ModelDefinition) super.clone();
		clone.props = (List<String>) ((ArrayList<String>)props).clone();
		
		//the value, IValueProvider, IModelRule do not need deep copy,just share
		clone.propValues = (Map<String, Object>) ((HashMap<String, Object>)propValues).clone();
		clone.propValueProviders = (Map<String, IValueProvider>) ((HashMap<String, IValueProvider>)propValueProviders).clone();
		clone.rules = (List<IModelRule>) ((ArrayList<IModelRule>)rules).clone();
		return clone;
	}
}
